﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Velocity4Net.Ctx;
using Velocity4Net.Runtime.Logging;
using Velocity4Net.Runtime.Parse.AST;

namespace Velocity4Net.Runtime.Directives
{/**
 * Directive that puts an unrendered AST block in the context
 * under the specified key, postponing rendering until the
 * reference is used and rendered.
 *
 * @author Andrew Tetlaw
 * @author Nathan Bubna
 * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a>
 * @since 1.7
 * @version $Id: Block.java 686842 2008-08-18 18:29:31Z nbubna $
 */
    public abstract class Block : Directive
    {
        protected INode block;
        protected Log log;
        protected int maxDepth;
        protected String key;

        /**
         * Return type of this directive.
         */
        public override int Type
        {
            get { return 1; }
        }

        /**
         *  simple init - get the key
         */
        public override void Init(RuntimeServices rs, InternalContextAdapter context, INode node)
        {
            base.Init(rs, context, node);

            log = rs.getLog();

            /**
             * No checking is done. We just grab the last child node and assume
             * that it's the block!
             */
            block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        }

        public bool render(InternalContextAdapter context, TextWriter writer)
        {
            preRender(context);
            try
            {
                return block.Render(context, writer);
            }
            catch (IOException e)
            {
                String msg = "Failed to render " + id(context) + " to writer "
                  + " at " + Log.formatFileString(this);

                log.error(msg, e);
                throw new ApplicationException(msg, e);
            }
            catch (StopCommand stop)
            {
                if (!stop.isFor(this))
                {
                    throw stop;
                }
                return true;
            }
            finally
            {
                postRender(context);
            }
        }

        /**
         * Creates a string identifying the source and location of the block
         * definition, and the current template being rendered if that is
         * different.
         */
        protected String id(InternalContextAdapter context)
        {
            StringBuilder str = new StringBuilder(100)
                .Append("block $").Append(key);
            if (!context.CurrentTemplateName.Equals(TemplateName))
            {
                str.Append(" used in ").Append(context.CurrentTemplateName);
            }
            return str.ToString();
        }

        /**
         * actual class placed in the context, holds the context
         * being used for the render, as well as the parent (which already holds
         * everything else we need).
         */
        public class Reference : Renderable
        {
            private InternalContextAdapter context;
            private Block parent;
            private int depth;

            public Reference(InternalContextAdapter context, Block parent)
            {
                this.context = context;
                this.parent = parent;
            }

            /**
             * Render the AST of this block into the writer using the context.
             */
            public bool Render(InternalContextAdapter context, TextWriter writer)
            {
                depth++;
                if (depth > parent.maxDepth)
                {
                    /* this is only a debug message, as recursion can
                     * happen in quasi-innocent situations and is relatively
                     * harmless due to how we handle it here.
                     * this is more to help anyone nuts enough to intentionally
                     * use recursive block definitions and having problems
                     * pulling it off properly.
                     */
                    parent.log.debug("Max recursion depth reached for " + parent.id(context)
                        + " at " + Log.formatFileString(parent));
                    depth--;
                    return false;
                }
                else
                {
                    parent.render(context, writer);
                    depth--;
                    return true;
                }
            }

            public override String ToString()
            {
                TextWriter writer = new StringWriter();
                if (Render(context, writer))
                {
                    return writer.ToString();
                }
                return null;
            }
        }
    }
}
